using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using RideMe.Core;

namespace RideMe.AddIns.SourceControl.SVN
{
    /// <summary>
    /// This is the process class for executing command line actions using the Subversion command line tool, and getting the results back
    /// </summary>
    public class SubversionProcess : ProcessBase
    {
        #region Private Variables

        private string _data = null;
        private SubversionAction _action;

        #endregion

        #region Events

        public event EventHandler<InfoEventArgs> InfoReceived;
        public event EventHandler<StatusEventArgs> StatusReceived;
        public event EventHandler CommitCompleted;
        public event EventHandler UpdateCompleted;
        public event EventHandler AddCompleted;

        #endregion

        #region Methods

        /// <summary>
        /// This allows the info received event to be raised to subscribers
        /// </summary>
        /// <param name="e"></param>
        private void OnInfoReceived(InfoEventArgs e)
        {
            if (InfoReceived != null)
                InfoReceived(this, e);
        }

        /// <summary>
        /// This allows the status received event to be raised to subscribers
        /// </summary>
        /// <param name="e"></param>
        private void OnStatusReceived(StatusEventArgs e)
        {
            if (StatusReceived != null)
                StatusReceived(this, e);
        }

        /// <summary>
        /// This allows the commit completed event to be raised to subscribers 
        /// </summary>
        /// <param name="e"></param>
        public void OnCommitCompleted(EventArgs e)
        {
            if (CommitCompleted != null)
                CommitCompleted(this, e);
        }

        /// <summary>
        /// This allows the update completed event to be raised to subscribers
        /// </summary>
        /// <param name="e"></param>
        public void OnUpdateCompleted(EventArgs e)
        {
            if (UpdateCompleted != null)
                UpdateCompleted(this, e);
        }

        /// <summary>
        /// This allows the add completed event to be raised to subscribers
        /// </summary>
        /// <param name="e"></param>
        public void OnAddCompleted(EventArgs e)
        {
            if (AddCompleted != null)
                AddCompleted(this, e);
        }

        /// <summary>
        /// This actions the call for information based on a path, recursively
        /// </summary>
        /// <param name="path"></param>
        public void GetInfo(string path)
        {
            _action = SubversionAction.INFO;
            RunCommand(string.Format("info {0} -R --xml", path), true);
        }

        /// <summary>
        /// This actions the call for status based on a path
        /// </summary>
        /// <param name="path"></param>
        public void GetStatus(string path)
        {
            _action = SubversionAction.STATUS;
            RunCommand(string.Format("status {0} --xml", path), true);
        }

        /// <summary>
        /// This actions the call to commit a path
        /// </summary>
        /// <param name="path"></param>
        /// <param name="message"></param>
        public void Commit(string path, string message)
        {
            _action = SubversionAction.COMMIT;
            RunCommand(string.Format("commit -m {0} {1}", message, path), true);
        }

        /// <summary>
        /// This actions the call to add a path
        /// </summary>
        /// <param name="path"></param>
        public void Add(string path)
        {
            _action = SubversionAction.ADD;
            RunCommand(string.Format("add {0}", path), true);
        }

        /// <summary>
        /// This actions the call to update a path
        /// </summary>
        /// <param name="path"></param>
        public void Update(string path)
        {
            _action = SubversionAction.UPDATE;
            RunCommand(string.Format("update {0}", path), true);
        }

        #endregion

        #region ProcessBase Members

        /// <summary>
        /// This overrides the run command to execute Subversion
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="redirectStandardError"></param>
        public override void RunCommand(string arguments, bool redirectStandardError)
        {
            _data = null;
            ProcessExecutor processExecutor = this.CreateCommand();
            processExecutor.ExecuteCommand(Properties.Resources.SubversionPath, arguments, redirectStandardError);
        }

        /// <summary>
        /// This overrides the receiving of output so as to build up the output in a member variable
        /// </summary>
        /// <param name="e"></param>
        protected override void OnOutputReceived(System.Diagnostics.DataReceivedEventArgs e)
        {
            _data += e.Data;
        }

        /// <summary>
        /// This overrides the completion event so as to know when to load the built up output as Xml to work with it
        /// </summary>
        /// <param name="e"></param>
        protected override void OnCompleted(EventArgs e)
        {
            while (!this.TargetProcess.HasExited) ;
            switch (_action)
            {
                case SubversionAction.INFO:
                case SubversionAction.STATUS:
                    ProcessXml();
                    break;
                case SubversionAction.ADD:
                    OnAddCompleted(new EventArgs());
                    break;
                case SubversionAction.COMMIT:
                    OnCommitCompleted(new EventArgs());
                    break;
                case SubversionAction.UPDATE:
                    OnUpdateCompleted(new EventArgs());
                    break;
            }
        }

        /// <summary>
        /// This processes the response data as Xml, which is how the "info" and the "status" calls are returned
        /// </summary>
        private void ProcessXml()
        {
            //Validate data
            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(_data);
            }
            catch
            {
                return;
            }
            //Decide what SVN action to take based on Xml
            switch (doc.DocumentElement.Name)
            {
                case "info":
                    OnInfoReceived(new InfoEventArgs(Info.FromXml<Info>(_data)));
                    break;
                case "status":
                    OnStatusReceived(new StatusEventArgs(Info.FromXml<Status>(_data)));
                    break;
            }
        }

        #endregion
    }

    /// <summary>
    /// This is used to specify which SVN action is being called currently
    /// </summary>
    public enum SubversionAction
    {
        INFO,
        STATUS,
        COMMIT,
        UPDATE,
        ADD
    }

    /// <summary>
    /// These get raised when an svn info is run
    /// </summary>
    public class InfoEventArgs : EventArgs
    {
        #region Constructor

        /// <summary>
        /// This builds the event args from the SVN info
        /// </summary>
        /// <param name="info"></param>
        public InfoEventArgs(Info info)
            : base()
        {
            _info = info;
        }

        #endregion

        #region Private Variables

        private Info _info;

        #endregion

        #region Properties

        /// <summary>
        /// This returns the SVN info exposed by this event
        /// </summary>
        public Info Info
        {
            get { return _info; }
        }

        #endregion
    }

    /// <summary>
    /// These get raised when an svn status is run
    /// </summary>
    public class StatusEventArgs : EventArgs
    {
        #region Constructor

        /// <summary>
        /// This builds the event args from SVN status
        /// </summary>
        /// <param name="status"></param>
        public StatusEventArgs(Status status)
            : base()
        {
            _status = status;
        }

        #endregion

        #region Private Variables

        private Status _status;

        #endregion

        #region Properties

        /// <summary>
        /// This returns the status object exposed by these event args
        /// </summary>
        public Status Status
        {
            get { return _status; }
        }

        #endregion
    }

    /// <summary>
    /// This represents the info returned from a Subversion info call
    /// </summary>
    [XmlRoot("info")]
    public class Info : IXmlSerializable
    {
        #region Private Variables

        private Collection<Entry> _entries = new Collection<Entry>();

        #endregion

        #region Properties

        /// <summary>
        /// This contains all entries returned within the info call
        /// </summary>
        public Collection<Entry> Entries
        {
            get { return _entries; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// This finds and returns the entry with the specified path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public Entry Find(string path)
        {
            foreach (Entry entry in _entries)
            {
                if (entry.Path == path)
                    return entry;
            }
            return null;
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// This creates an object from Xml
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        public static T FromXml<T>(string xml)
        {
            try
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                MemoryStream stream = new MemoryStream();
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.Write(xml);
                    writer.Flush();
                    stream.Seek(0, SeekOrigin.Begin);
                    return (T)serializer.Deserialize(stream);
                }
            }
            catch
            {
                return default(T);
            }
        }

        /// <summary>
        /// This gets Xml from an object
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string ToXml(object obj)
        {
            XmlSerializer serializer = new XmlSerializer(obj.GetType());
            MemoryStream stream = new MemoryStream();
            serializer.Serialize(stream, obj);
            stream.Seek(0, SeekOrigin.Begin);
            using (StreamReader reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }

        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// This isn't necessary and so isn't implemented
        /// </summary>
        /// <returns></returns>
        public XmlSchema GetSchema()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Fills out the object from Xml
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            while (!reader.EOF)
            {
                if (reader.Name == "entry" && reader.NodeType == XmlNodeType.Element)
                    _entries.Add(Info.FromXml<Entry>(reader.ReadOuterXml()));
                else
                    reader.Read();
            }
        }

        /// <summary>
        /// Writes out the object to Xml
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("info");
            foreach (Entry entry in _entries)
            {
                writer.WriteRaw(Info.ToXml(entry));
            }
            writer.WriteEndElement();
        }

        #endregion
    }

    /// <summary>
    /// This represents the status info returned from a Subversion status call
    /// </summary>
    [XmlRoot("status")]
    public class Status : IXmlSerializable
    {
        #region Private Variables

        private Collection<Target> _targets = new Collection<Target>();

        #endregion

        #region Properties

        public Collection<Target> Targets
        {
            get { return _targets; }
            set { _targets = value; }
        }

        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// This isn't necessary and so isn't implemented
        /// </summary>
        /// <returns></returns>
        public XmlSchema GetSchema()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Fills out the object from Xml
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            while (!reader.EOF)
            {
                if (reader.Name == "target" && reader.NodeType == XmlNodeType.Element)
                    _targets.Add(Info.FromXml<Target>(reader.ReadOuterXml()));
                else
                    reader.Read();
            }
        }

        /// <summary>
        /// Writes out the object to Xml
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("status");
            foreach (Target target in _targets)
            {
                writer.WriteRaw(Info.ToXml(target));
            }
            writer.WriteEndElement();
        }

        #endregion
    }

    /// <summary>
    /// This represents the serializable entry element, one for each file or directory being queried
    /// </summary>
    [XmlRoot("entry")]
    public class Entry
    {
        #region Private Variables

        private string _kind;
        private string _path;
        private string _revision;        
        private Repository _repository;
        private WcInfo _wcInfo;
        private WcStatus _wcStatus;
        private Commit _commit;

        #endregion

        #region Properties

        [XmlAttribute("kind")]
        public string Kind
        {
            get { return _kind; }
            set { _kind = value; }
        }

        [XmlAttribute("path")]
        public string Path
        {
            get { return _path; }
            set { _path = value; }
        }

        [XmlAttribute("revision")]
        public string Revision
        {
            get { return _revision; }
            set { _revision = value; }
        }

        [XmlElement("repository")]
        public Repository Repository
        {
            get { return _repository; }
            set { _repository = value; }
        }

        [XmlElement("wc-info")]
        public WcInfo WcInfo
        {
            get { return _wcInfo; }
            set { _wcInfo = value; }
        }

        [XmlElement("wc-status")]
        public WcStatus WcStatus
        {
            get { return _wcStatus; }
            set { _wcStatus = value; }
        }

        [XmlElement("commit")]
        public Commit Commit
        {
            get { return _commit; }
            set { _commit = value; }
        }

        #endregion
    }

    [XmlRoot("repository")]
    public class Repository
    {
        #region Private Variables

        private string _root;
        private string _uuid;

        #endregion

        #region Properties

        [XmlElement("root")]
        public string Root
        {
            get { return _root; }
            set { _root = value; }
        }

        [XmlElement("uuid")]
        public string UUID
        {
            get { return _uuid; }
            set { _uuid = value; }
        }

        #endregion
    }

    [XmlRoot("wc-info")]
    public class WcInfo
    {
        #region Private Variables

        private string _schedule;

        #endregion

        #region Properties

        [XmlElement("schedule")]
        public string Schedule
        {
            get { return _schedule; }
            set { _schedule = value; }
        }

        #endregion
    }

    [XmlRoot("wc-status")]
    public class WcStatus
    {
        #region Private Variables

        private string _props;
        private string _item;
        private string _revision;
        private Commit _commit;

        #endregion

        #region Properties

        [XmlAttribute("props")]
        public string Props
        {
            get { return _props; }
            set { _props = value; }
        }

        [XmlAttribute("item")]
        public string Item
        {
            get { return _item; }
            set { _item = value; }
        }

        [XmlAttribute("revision")]
        public string Revision
        {
            get { return _revision; }
            set { _revision = value; }
        }

        [XmlElement("commit")]
        public Commit Commit
        {
            get { return _commit; }
            set { _commit = value; }
        }

        #endregion
    }

    [XmlRoot("target")]
    public class Target : IXmlSerializable
    {
        #region Private Variables

        private string _path;
        private Collection<Entry> _entries = new Collection<Entry>();

        #endregion

        #region Properties

        public string Path
        {
            get { return _path; }
            set { _path = value; }
        }

        public Collection<Entry> Entries
        {
            get { return _entries; }
        }

        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// This isn't necessary and so isn't implemented
        /// </summary>
        /// <returns></returns>
        public XmlSchema GetSchema()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Fills out the object from Xml
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            while (!reader.EOF)
            {
                if (reader.Name == "path" && reader.NodeType == XmlNodeType.Attribute)
                    _path = reader.Value;
                else if (reader.Name == "entry" && reader.NodeType == XmlNodeType.Element)
                    _entries.Add(Info.FromXml<Entry>(reader.ReadOuterXml()));
                else
                    reader.Read();
            }
        }

        /// <summary>
        /// Writes out the object to Xml
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("target");
            writer.WriteAttributeString("path", this.Path);
            foreach (Entry entry in _entries)
            {
                writer.WriteRaw(Info.ToXml(entry));
            }
            writer.WriteEndElement();
        }

        #endregion
    }

    [XmlRoot("commit")]
    public class Commit
    {
        #region Private Variables

        private string _revision;
        private string _author;
        private string _date;

        #endregion

        #region Properties

        [XmlAttribute("revision")]
        public string Revision
        {
            get { return _revision; }
            set { _revision = value; }
        }

        [XmlElement("author")]
        public string Author
        {
            get { return _author; }
            set { _author = value; }
        }

        [XmlElement("date")]
        public string Date
        {
            get { return _date; }
            set { _date = value; }
        }

        #endregion
    }
}
